<?php
/**
 * Class for storing long-term persistent information
 */
class App_Cookie
{
    /**
     * Expire duration for cookies, defaults to one year.
     *
     * @var integer
     */
    protected static $_duration = 31104000;

    /**
     * Secret for blowfish encryption
     * 
     * @var string
     */
    protected static $_blowfishSecret;

    /**
     * Wether we are in unit test mode
     *
     * @var boolean
     */
    protected static $_unitTest = false;

    /**
     * Set wether we are in unit test mode
     * 
     * @param  string $flag
     * @return void
     */
    public static function setUnitTest($flag)
    {
        self::$_unitTest = (bool) $flag;
    }

    /**
     * Set expire duration
     * 
     * @param  integer $duration
     * @return void
     */
    public static function setDuration($duration)
    {
        self::$_duration = $duration;
    }

    /**
     * Set blowfish secret
     *
     * @param  string $secret
     * @return void
     */
    public static function setBlowfishSecret($secret)
    {
        self::$_blowfishSecret = $secret;
    }

    /**
     * Get the blowfish secret
     *
     * @return string
     */
    public static function getBlowfishSecret()
    {
        if (self::$_blowfishSecret === null) {
            throw new App_RuntimeException('No blowfish secret was set');
        }

        return self::$_blowfishSecret;
    }

    /**
     * Get a value from the storage
     *
     * If it does not exist, null is returned
     *
     * @param  string $name
     * @param  string $default
     * @return string|null
     */
    public static function get($name, $default = null)
    {
        return (isset($_COOKIE[$name]) ? $_COOKIE[$name] : $default);
    }

    /**
     * Set a value in the storage
     *
     * @param  string $name
     * @param  string $value
     * @return void
     */
    public static function set($name, $value)
    {
        // @codeCoverageIgnoreStart
        if (self::$_unitTest === false) {
            setcookie($name, $value, time() + self::$_duration, '/');
        }
        // @codeCoverageIgnoreEnd

        $_COOKIE[$name] = $value;
    }

    /**
     * Get a value from the storage and decrypt it
     *
     * If it does not exist, null is returned
     *
     * @param  string $name
     * @param  string $default
     * @return string|null
     */
    public static function getEncrypted($name, $default = null)
    {
        $value = self::get($name, null);

        if ($value === null) {
            return $default;
        }

        $encodedData = base64_decode($value, true);

        if ($encodedData === false) {
            throw new App_RuntimeException('Invalid encoded data found in cookie');
        }

        return rtrim(mcrypt_decrypt(MCRYPT_BLOWFISH, self::getBlowfishSecret(), $encodedData, MCRYPT_MODE_CBC, self::_getIv(false)), "\0");
    }

    /**
     * Encrypt and set a value in the storage
     *
     * @param  string $name
     * @param  string $value
     * @return void
     */
    public static function setEncrypted($name, $value)
    {
        self::set($name, base64_encode(mcrypt_encrypt(MCRYPT_BLOWFISH, self::getBlowfishSecret(), $value, MCRYPT_MODE_CBC, self::_getIv(true))));
    }

    /**
     * Clear a value
     *
     * @param  string $name
     * @return void
     */
    public static function clear($name)
    {
        if (isset($_COOKIE[$name])) {
            // @codeCoverageIgnoreStart
            if (self::$_unitTest === false) {
                setcookie($name, '', 0, '/');
            }
            // @codeCoverageIgnoreEnd
            
            unset($_COOKIE[$name]);
        }
    }

    /**
     * Get IV and generate it if required, else throw an exception
     *
     * @param  boolean $generate
     * @return string
     */
    protected static function _getIv($generate)
    {
        if (($iv = self::get('mcrypt_iv')) === null) {
            if (!$generate) {
                // @codeCoverageIgnoreStart
                throw new App_RuntimeException('IV missing in cookie');
                // @codeCoverageIgnoreEnd
            }

            srand((double) microtime() * 1000000);

            $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');

            if ($td === false) {
                // @codeCoverageIgnoreStart
                throw new App_RuntimeException('Could not open mcrypt module');
                // @codeCoverageIgnoreEnd
            }

            $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);

            self::set('mcrypt_iv', base64_encode($iv));
        } else {
            $iv = base64_decode($iv, true);

            if ($iv === false) {
                throw new App_RuntimeException('Invalid data found in IV cookie');
            }
        }

        return $iv;
    }
}

